// Image functions
//
#include <stdio.h>
#include <stdlib.h>         // for malloc
#include <string.h>
#include "Common/Debug.h"
#include "Common/Macros.h"
#include "Common/FileSys.h"
#include "Common/Memory.h"
#include "Color.h"
#include "Image.h"
#include "Draw.h"

#ifdef _15BITS_COLOR
void g_AdjustImage565to555(TImage *image)
{
    int len, pad;
    UInt32 pixel, *data;

    data = (UInt32*) image->data;
    len = image->width * image->height;  
    pad = len & 0x01;   // even or odd?
    len >>= 1;          // how many 2 pixels

    while (len--)
    {
        // Deal 2 pixels once a time
        pixel = *data;
        *data= (pixel & 0x001F001F) | ((pixel >> 1) & 0x7FE07FE0);
        data++;
    }

    if (pad)
    {
        // Remain one pixel
        pixel = *(UInt16 *) data;
        *(UInt16 *) data = (UInt16) ((pixel & 0x01F) | ((pixel >> 1) & 0x07FE0));
    }
}
#endif

#ifdef __RGB24
void g_AdjustImage888BGRtoGBR(TImage *image)
{
    int len;
    UInt8 ch, *data;

    data = image->data;
    len = image->width * image->height;  

    while (len--)
    {
        // Deal 1 pixel (triple byte) once a time
        ch = *data;
        *data = *(data+1);
		*(data+1) = ch;
        data += 3;
    }
}
#endif

// Bill test Load a image from file
int g_BillLoadImage(char* tImagebuffer, const char *filename)
{
    FILE *fp;
    long size;

    fp = fs_Open(filename, "rb");

    if (fp == 0)
    {
        TRACE("LoadImage: file '%s' not found!\n", filename);
        return 0;        // failed! file not found
    }

    size = fs_GetFileLen(fp);

    fs_Read((void*) tImagebuffer, size, fp);

    fs_Close(&fp);

    return 1;
}

// Load a image from file
TImage* g_LoadImage(const char *filename)
{
    FILE *fp;
    long size;
    TImage* image;

    fp = fs_Open(filename, "rb");

    if (fp == 0)
    {
        TRACE("LoadImage: file '%s' not found!\n", filename);
        return NULL;        // failed! file not found
    }

    size = fs_GetFileLen(fp);

    image = (TImage *) ut_malloc(size);

    if (image == NULL)
    {
        TRACE("LoadImage: memory failed!\n");
        fs_Close(&fp);
        return NULL;    // failed! memory not enough
    }

    fs_Read((void*) image, size, fp);

#ifdef _15BITS_COLOR
    g_AdjustImage565to555(image);
#endif

    fs_Close(&fp);

    return image;
}

// Create a image from memory
TImage* g_CreateImage(int width, int height)
{
	TImage* image;

	image = (TImage*) ut_malloc(width * height * PIXEL_SIZE + sizeof(UInt16) * 2);

	ASSERT_DEBUG(image);

	if (image == NULL)
		LOG("g_CreateImage: fail to allocate memory!\n");

	image->width = width;
	image->height = height;

	return image;
}

// Delete a image from memory
int  g_DeleteImage(TImage *image)
{
    SAFE_FREE(image);

    return 1;
}

#if __IMAGE_LIST_CACHE__
/** Charlie 20060907 begin **************************************************************/
TImageList* g_LoadImageList(const char *filename)
{
	FILE 			*fp;
	TImageList	 	*pitemlist; // returned pointer
	unsigned char 	*tmp;
	int 			read_size, i, page_num, item_size, max_item_size=0, cache_size;
	unsigned long	offset1, offset2, flen=0;
	
	fp = fs_Open(filename, "rb");
	if (fp == NULL)
	{
		TRACE("Load: file '%s' not found!\n", filename);
		return NULL;
	}
	
	pitemlist = (TImageList*) ut_malloc( sizeof(TImageList) );
	if (pitemlist == NULL)
	{
		TRACE("Memory fail.\n");
		fs_Close(&fp);
		return NULL;
	}
	
	// save filename
	pitemlist->filename = (char*)ut_malloc( strlen(filename) + 1 );	// allocate memory for storing filename
	strncpy(pitemlist->filename, filename, strlen(filename) + 1);	// save filename
	pitemlist->filename[strlen(filename)] = 0; // make filename a null-terminated string
	
	// get item count in file
	fs_Read( &pitemlist->Count, sizeof(unsigned short), fp);
	
	// get offset (EOF position included)
	read_size = sizeof(unsigned long) * ( pitemlist->Count + 1);
	tmp = (unsigned char*)ut_malloc(read_size);
	if (tmp == NULL)
	{	
		TRACE("Memory fail: allocates ImageItemList->Offsets fail.\n");
		fs_Close(&fp);
		ut_free(pitemlist->filename);
		ut_free(pitemlist);
		return NULL;
	}
	fs_Read(tmp, read_size, fp);
	pitemlist->Offsets = (unsigned long*)tmp;
	
	flen = fs_GetFileLen(fp);
	fs_Close(&fp);

	// find max size of data item
	for (i = 0; i< (pitemlist->Count); i++)  // calculate 'Count' times
	{	
		offset1 = (unsigned long) *( pitemlist->Offsets + i );
		offset2 = (unsigned long) *( pitemlist->Offsets + i + 1 );
		item_size = (int) (offset2 - offset1);

		if (item_size > MAX_IMAGE_LOAD_SIZE)
		{
			// Note: this image too large, which very wastes memory space, and we decide
			//		 to drop it, and relative UI use program method to draw it
			LOG("g_LoadImageList: image #%d too big, will never load!\n", i);
		}
		else if (item_size > max_item_size)
		{
			max_item_size = item_size;
		}
	}

	// check max_data_size
	cache_size = flen / 3;		// NOTE: this is nice idea when file size is small but with one or two large item!? -OB
	if (max_item_size > cache_size)
		page_num = 2;			// NOTE: when page_num is so small, it will cause when multiple call g_GetImageFromList() w/o drawing,
								//       the later called will kick early (before early one drawing!) -OB
	else
		page_num = cache_size / max_item_size;
	//page_num = MAX_IMAGE_CACHE_MEM_SIZE / max_item_size;
	//page_num = max(page_num, MIN_IMAGE_CACHE_PAGE_NUM); // get the bigger one
	
	// create cache
	pitemlist->pCache = (TCache*) ut_malloc(sizeof(TCache));
	// to do: check memory
	pitemlist->pCache->unPageNum = page_num;
	pitemlist->pCache->unPageSize = max_item_size;
	pitemlist->pCache->unPageUsed = 0;
	
	pitemlist->pCache->pPageInfo = (TPageInfo*) ut_malloc( sizeof(TPageInfo) * page_num );
	if (pitemlist->pCache->pPageInfo == NULL)
	{
		// to do: free memory
		return NULL;
	}
	pitemlist->pCache->pPage = (char *) ut_malloc( max_item_size * page_num);
	if (pitemlist->pCache->pPage == NULL)
	{
		// to do: free memory
		return NULL;
	}
	
	// reset page info
	for(i = 0; i< page_num; i++)
	{
		pitemlist->pCache->pPageInfo[i].nSerial = -1;
		pitemlist->pCache->pPageInfo[i].nHit = 0;
	}
	
	return pitemlist;
}

// Delete a list of images from memory
int  g_DeleteImageList(TImageList *pitemlist)
{
	// free pageinfo in cache
	SAFE_FREE(pitemlist->pCache->pPageInfo);
	// free page in cache
	SAFE_FREE(pitemlist->pCache->pPage);
	// free offsets in itemlist
    SAFE_FREE(pitemlist->Offsets);
    // free filename in itemlist
    SAFE_FREE(pitemlist->filename);
    // free itemlist itself
    SAFE_FREE(pitemlist);

    return 1;
}

// Get a data item from list
TImage*  g_GetImageFromList(const TImageList *pitemlist, int serial)
{
#if defined _15BITS_COLOR
	static char dummy_1x1_image[] = { 0x01, 0x00, 0x01, 0x00, 0x00, 0x00 };
#else
	static char dummy_1x1_image[] = { 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00 };
#endif

	int i, page_num, page_used, page_size;
	int page_id = -1;
	int item_size, least_hit=0;
	unsigned long offset1, offset2;
	TImage *pimage_item;
	FILE *fp;

	// load some numbers for convenient use	
	page_num = pitemlist->pCache->unPageNum;
	page_size = pitemlist->pCache->unPageSize;
	page_used = pitemlist->pCache->unPageUsed;

	// first find if serial already in cache
	for (i = 0; i< page_used; i++)
	{
		if (pitemlist->pCache->pPageInfo[i].nSerial == serial) // found
		{
			page_id = i;
			break;
		}
	}
	
	if (page_id != -1)  // found
	{
		pitemlist->pCache->pPageInfo[page_id].nHit++; // increase hit count
		if (pitemlist->pCache->pPageInfo[page_id].nHit == UINT16_MAX )
		{
			// counter reset!
			pitemlist->pCache->pPageInfo[page_id].nHit = 0;
		}
		pimage_item = (TImage*) ( (pitemlist->pCache->pPage) + (page_size * page_id) ); // find memory address
	}
	else // not found, load data from file
	{
		offset1 = (unsigned long) pitemlist->Offsets[serial];
		offset2 = (unsigned long) pitemlist->Offsets[serial+1];
		item_size = offset2 - offset1;

		if (item_size > MAX_IMAGE_LOAD_SIZE)
		{
			// Note: this image too large, which very wastes memory space, and we decide
			//		 to drop it, and relative UI need to use program method to draw it
			LOG("g_LoadImageList: image #%d too big, will never load!\n", i);

			return (TImage*) dummy_1x1_image;
		}

		if (page_used < page_num) // add to tail
		{
			page_id = page_used; // get available page_id

			// adjust Cache attribute
			pitemlist->pCache->unPageUsed++;
		}
		else // find out the rarest hit page and then fill to the page position
		{
			// find out the rarest hit page
			least_hit = pitemlist->pCache->pPageInfo[0].nHit;
			page_id = 0;

			for(i = 1; i < (int)pitemlist->pCache->unPageNum; i++)
			{
				// found page with less hit than current one
				if (pitemlist->pCache->pPageInfo[i].nHit < least_hit)
				{
					least_hit = pitemlist->pCache->pPageInfo[i].nHit;
					page_id = i;
				}
			}
		}

		// load data item from file and write to page of cache
		fp = fs_Open(pitemlist->filename, "rb");
		fs_Seek(fp, offset1, SEEK_SET);  // set file position to offset of data item
		fs_Read( (char*) (pitemlist->pCache->pPage + page_size*page_id) , item_size, fp); // read data item
		fs_Close(&fp);

#if defined _15BITS_COLOR
	    g_AdjustImage565to555( (TImage*) (pitemlist->pCache->pPage + page_size*page_id) );
#endif
		// assign page info
		pitemlist->pCache->pPageInfo[page_id].nSerial = serial;
		pitemlist->pCache->pPageInfo[page_id].nHit = 1;
		pimage_item = (TImage*) ( pitemlist->pCache->pPage + page_size*page_id);
	}

	return pimage_item;
}
/** Charlie 20060907 end ****************************************************************/
#else
// Load list of images from file
TImageList* g_LoadImageList(const char *filename)
{
    FILE *fp;
    long size;
    TImageList* plist;
#if defined _15BITS_COLOR
    int i;
    TImage *image;
#endif

    plist = (TImageList *) ut_malloc(sizeof(TImageList));

    ASSERT_ALWAYS(plist != NULL);

    fp = fs_Open(filename, "rb");

    if (fp == 0)
    {
        TRACE("LoadImageList: file '%s' not found!\n", filename);
        return NULL;        // failed! file not found
    }

    size = fs_GetFileLen(fp);

    plist->pBuff = (char *) ut_malloc(size);

    if (plist->pBuff == NULL)
    {
        TRACE("LoadImage: memory failed!\n");
        fs_Close(&fp);
        return NULL;    // failed! memory not enough
    }

    fs_Read(plist->pBuff, size, fp);

    fs_Close(&fp);

    plist->Count = *(UInt16*) plist->pBuff;

#if defined _15BITS_COLOR
    for (i = 0; i < plist->Count; i++)
    {
        image = g_GetImageFromList(plist, i);
        g_AdjustImage565to555(image);
    }
#endif

    return plist;    
}

// Delete a list of images from memory
int  g_DeleteImageList(TImageList *image_list)
{
    SAFE_FREE(image_list->pBuff);

    return 1;
}

// Get a image from list
TImage*  g_GetImageFromList(const TImageList *image_list, int serial)
{
    UInt32 offset;

    ASSERT_DEBUG(image_list && image_list->pBuff);

    ASSERT_DEBUG(serial >= 0 && serial < image_list->Count);

	offset = _UINT32(image_list->pBuff + sizeof(UInt16) + serial * sizeof(UInt32));

	// NOTE: make sure the address must be even aligned otherwise it will has
	//		 a memory fault in PL1061 target board when drawing this image
	ASSERT_DEBUG(((UInt32) (image_list->pBuff + offset) & 1) == 0);

    return (TImage*) (image_list->pBuff + offset);
}
#endif

// Draw images
void g_DrawMultiImages(const TCanvas *canvas, TPoint *points, int count, const TImageList *image_list, int *serials)
{
    int i;
    TImage *image;

    for (i = 0; i < count; i++)
    {
        image = g_GetImageFromList(image_list, serials[i]);

        g_DrawImage(canvas, points[i].x, points[i].y, image);
    }
}
